//////////////////////////////////////////////////////////////////////////
//
// Generate shapes procedurally
//
// All triangles are generated "outward" facing.  If you want "inward" 
// facing triangles (for example, if you want to place the camera inside
// a sphere to simulate a sky), you will need to:
//   1. Change the Direct3D cull mode or manually reverse the winding order.
//   2. Invert the normal.
//   3. Update the texture coordinates and tangent vectors.
//////////////////////////////////////////////////////////////////////////

#pragma once

#include <cstdint>
#include <DirectXMath.h>
#include <vector>

struct VertexData {
    DirectX::XMFLOAT3 mPos;
    DirectX::XMFLOAT3 mNormal;
    DirectX::XMFLOAT3 mTangentU;
    DirectX::XMFLOAT2 mTexC;

    VertexData(const DirectX::XMFLOAT3& pos = DirectX::XMFLOAT3(0.0f, 0.0f, 0.0f), const DirectX::XMFLOAT3& normal = DirectX::XMFLOAT3(0.0f, 0.0f, 0.0f), 
               const DirectX::XMFLOAT3& tangentU = DirectX::XMFLOAT3(0.0f, 0.0f, 0.0f), const DirectX::XMFLOAT2& texC = DirectX::XMFLOAT2(0.0f, 0.0f));

    VertexData(const float posX, const float posY, const float posZ, const float normalX, const float normalY, const float normalZ,
               const float tangentUx,  const float tangentUy, const float tangentUz, const float texCU, const float texCV);
};

class GeometryGenerator {	
public:
	struct MeshData {
		std::vector<VertexData> mVertices;
		std::vector<uint32_t> mIndices;
	};

	// Creates a box centered at the origin with the given dimensions.
	static void generateBox(const float width, const float height, const float depth, MeshData& meshData);

	// Creates a sphere centered at the origin with the given radius. 
	// The slices and stacks parameters control the degree of tessellation.
	static void generateSphere(const float radius, const uint32_t sliceCount, const uint32_t stackCount, MeshData& meshData);

	// Creates a geosphere centered at the origin with the given radius.  
	// The depth controls the level of tessellation.
	static void generateGeosphere(const float radius, const uint32_t numSubdivisions, MeshData& meshData);

	// Creates a cylinder parallel to the y-axis, and centered about the origin.  
	// The bottom and top radius can vary to form various cone shapes rather than true
	// cylinders. The slices and stacks parameters control the degree of tessellation.
	static void generateCylinder(const float bottomRadius, const float topRadius, const float height, const uint32_t sliceCount, const uint32_t stackCount, MeshData& meshData);

	// Creates an mxn grid in the xz-plane with numRows rows and numRows columns, 
	// centered at the origin with the specified width and depth.
	static void generateGrid(const float width, const float depth, const uint32_t numRows, const uint32_t numColumns, MeshData& meshData);

	// Creates an mxn grid in the xz-plane with numRows rows and numRows columns, 
	// centered at the origin with the specified width and depth.
	static void generateGridForInterlockingTiles(const float width, const float depth, const uint32_t numRows, const uint32_t numColumns, MeshData& meshData);

	// Creates a quad covering the screen in NDC coordinates.  
	// This is useful for postprocessing effects.
	static void generateFullscreenQuad(MeshData& meshData);

	// Quad centered at the origin (0.0f, 0.0f, 0.0f)
	// of unit length
	static void generateUnitQuad(MeshData& meshData);

private:
	static void subdivide(MeshData& meshData);
	static void buildCylinderTopCap(const float topRadius, const float height, const uint32_t sliceCount, MeshData& meshData);
	static void buildCylinderBottomCap(const float bottomRadius, const float height, const uint32_t sliceCount, MeshData& meshData);
};

